# -*- coding: utf-8 -*-


# cmap2owl -- Helper application to convert from concept maps to OWL ontologies
# Copyright (c) 2008-2013  Rodrigo Rizzi Starr
#  
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#  
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#  
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


'''
Contains a set of rules to identify superclass/subclass relationships

@author: Rodrigo Rizzi Starr
@copyright: Copyright © 2008-2013 Rodrigo Rizzi Starr
@license: MIT License
@contact: rodrigo.starr@gmail.com
'''


from mapperRule import MapperRule
from mapperQuestion import MapperQuestion
from multiQuestion import MultiQuestion
from infoProvider import InfoProvider, SimpleInfoProvider, UnionOfInfoProviders
from actionProvider import ActionProvider, UnionOfActionProviders
from log import log
log.debug("Loaded...")

            
class GeneralizationRule(MapperRule):
    "Finds generalization and classification relations"
    
    def __init__(self, db):
        
        # Linking phrases denoting a generalization or classification relation
        # Both relations are when the object of the phrase is more general than
        #   the subject
        # Generalizations relate classes to classes (LH is a RH)
        # Classifications relate individuals to classes (LH is an instance of RH)
        classifiers = [
                       "generalizationPhrases",
                       "classificationPhrases",
                        ]
          
        # Heuristics to identify generalization/specialization rules
        rules = [
                 "inferenceGroup(generalizationRule,"
                    "[generalization(_, _, _), classification(_, _, _)])",
                 
                 "generalization(Relation, FromSet, ToSet) :-"
                    "generalizationPhrases(Label),"
                    "label(Relation, Label),"
                    "positive(relation(Relation)),"
                    "forall(relationBetween(Relation, Subclass, Superclass),("
                            "positive(class(Subclass)),"
                            "positive(class(Superclass)))),"
                    "fromSetRelation(Relation, FromSet),"
                    "toSetRelation(Relation, ToSet)",
                    
                 "classification(Relation, FromSet, ToSet) :-"
                    "classificationPhrases(Label),"
                    "label(Relation,Label),"
                    "positive(relation(Relation)),"
                    "forall(relationBetween(Relation, Individual, Superclass),("
                            "positive(individual(Individual)),"
                            "positive(class(Superclass)))),"
                    "fromSetRelation(Relation, FromSet),"
                    "toSetRelation(Relation, ToSet)",
                   
                 "doubleGeneralization(Relation, FromSet, ToSet) :- "
                    "possible(generalization(Relation, FromSet, ToSet)),"
                    "possible(classification(Relation, FromSet, ToSet))",
                 
                 # Classification consequences
                 "isTrue(individual(Individual)) :-"
                   "isTrue(classification(Relation, _, _)),"
                   "fromPartRelation(Relation, Individual)",
                   
                 "isTrue(class(Class)) :-"
                   "isTrue(classification(Relation, _, _)),"
                   "toPartRelation(Relation, Class)",
                   
                 "classOfIndividualOWL(Individual,Class) :-"
                   "isTrue(classification(Relation, _, _)),"
                   "relationBetween(Relation, Individual, Class)",
                   
                  "isFalse(relation(Relation)) :-"
                    "isTrue(classification(Relation, _, _))",
                    
                 # Generalization consequences
                 "isTrue(class(Class)) :-"
                   "isTrue(generalization(Relation, _, _)),"
                   "fromPartRelation(Relation, Class)",
                   
                 "isTrue(class(Class)) :-"
                   "isTrue(generalization(Relation, _, _)),"
                   "toPartRelation(Relation, Class)",
                   
                 "subclassOWL(Subclass, Superclass) :-"
                    "isTrue(generalization(Relation, _, _)),"
                    "relationBetween(Relation, Subclass, Superclass)",
                    
                 "isFalse(relation(Relation)) :-"
                      "isTrue(generalization(Relation, _, _))",
                    ]
         
        MapperRule.__init__(self, "Generalizations", db, rules, classifiers)
          
    def getGeneralizationQuestions(self):
        "Returns a question list"
        questions = []
        query = "possible(generalization(Relation, FromSet, ToSet))," \
                "\+ possible(classification(Relation, FromSet, ToSet))"
        generalizations = list(self.db.query(query))
        
#        log.debug("  " + str(len(generalizations)) + " generalizations")
        
        for answer in generalizations:
            questions.append(GeneralizationQuestion(
                    self.db,
                    answer,
                    'The relation "<b>%(Relation)s</b>" seems to say that '
                        '"<b>%(FromSet)s</b>" is a type of "<b>%(ToSet)s</b>". '
                        'Is it true ?',
                     "generalization(%(Relation)s, FromSet, ToSet)",
                     helpFile = "generalization",
                     ))
            
        return questions
      
    def getClassificationQuestions(self):
        """Returns a list of questions if a certain phrase is an
        classification phrase"""
        
        questions = []
        
        desindQuery = "possible(classification(Relation, FromSet, ToSet))," \
                      "\+ possible(generalization(Relation, FromSet, ToSet))"
        
        desindRelations = list(self.db.query(desindQuery))
        
#        log.debug("  " + str(len(desindRelations)) + " desindRelations")
        
        for answer in desindRelations:
            answer["Ids"] = [answer["Relation"]]
            questions.append(MapperQuestion(
                self.db,
                answer,
                'The relation "<b>%(Relation)s</b>" seems to say that the '
                    'element(s) "<b>%(FromSet)s</b>" is(are) an individual(s) '
                    'of class "<b>%(ToSet)s</b>". Is it true ?',
                'classification(%(Relation)s, FromSet, ToSet)',
                 helpFile = "classification",
            ))
            
        return questions
      
    def getDoubleQuestions(self):
        """Returns a list of questions if a certain phrase is an
        classification or a generalization phrase"""
        
        questions = []
        
        desindQuery = "doubleGeneralization(Relation, FromSet, ToSet)"
        
        desindRelations = list(self.db.query(desindQuery))
        
#        log.debug("  " + str(len(desindRelations)) + " desindRelations")
        
        for answer in desindRelations:
            answer["Ids"] = [answer["Relation"]]
            answer["Ids"].extend(answer["FromSet"])
            answer["Ids"].extend(answer["ToSet"])
            
            questions.append(MultipleGeneralizationQuestion(
                self.db,
                answer,
                'Is (are) the element(s) "<b>%(FromSet)s</b>" individuals(s) '
                    'or subclass(es) of the class(es) "<b>%(ToSet)s</b>" ?',
                {'Individual(s)':
                    'classification(%(Relation)s, FromSet, ToSet)',
                 'Subclass(es)':
                    'generalization(%(Relation)s, FromSet, ToSet)',},
                 helpFile = "generalizationAndClassification",
            ))
            
        return questions
      
    def getQuestions(self):
        """Returns a list of questions"""
        
        questions = []
        questions.extend(self.getDoubleQuestions())
        questions.extend(self.getGeneralizationQuestions())
        questions.extend(self.getClassificationQuestions())
        
        return questions

    def getInfoProvider(self):
        "Returns an info provider about mereologic relations"
        
        generalizationRelationInfo = SimpleInfoProvider(self.db,
                            "isTrue(generalization(%s, _, _))",
                            "This relation is a generalization relation")
        
        specializationRelationInfo = SimpleInfoProvider(self.db,
                            "isTrue(classification(%s, _, _))",
                            "This relation is a classification relation")
        
        superInfo = InfoProvider(self.db,
                            "setof(Sup, subclassOWL(%s, Sup), Set)",
                            "This class has the following superclasses:")
        
        subInfo = InfoProvider(self.db,
                            "setof(Sub, subclassOWL(Sub, %s), Set)",
                            "This class has the following subclasses:")
        
        indInfo = InfoProvider(self.db,
                            "setof(Sup, classOfIndividualOWL(%s, Sup), Set)",
                            "This individual is of the following classes:")
        
        superIndInfo = InfoProvider(self.db,
                            "setof(Ind, classOfIndividualOWL(Ind, %s), Set)",
                            "The following individuals are of this class:")
        
        return UnionOfInfoProviders(generalizationRelationInfo,
                                    specializationRelationInfo,
                                    superInfo,
                                    subInfo,
                                    indInfo,
                                    superIndInfo)


    def getActionsProvider(self):
        """Returns actions providers for this rule"""
        
        generalizationProvider = ActionProvider(self.db,
                                  "equivalentLinkingPhrase(Element, %s),"
                                        "label(Element, Label),"
                                        "\+ generalizationPhrases(Label)",
                                  "Does this phrase "
                                        "indicate generalization ?",
                                  "generalizationPhrases(%(Label)s)")
        
        classificationProvider = ActionProvider(self.db,
                                  "equivalentLinkingPhrase(Element, %s),"
                                        "label(Element, Label),"
                                        "\+ classificationPhrases(Label)",
                                  "Does this phrase "
                                        "indicate classification ?",
                                  "classificationPhrases(%(Label)s)")
        
        
        return UnionOfActionProviders(generalizationProvider,
                                      classificationProvider)

      
class GeneralizationQuestion(MapperQuestion):
    "Better update of the Ids for the generalization relations"
    
    def updateIds(self):
        "Return better Ids"
        
        answer = self.db.query(self.queryIds)
        try:
            answer = answer.next()
        except StopIteration:
            self.ids = set([])
        else:
            ids = [self.data["Relation"]]
            ids.extend(answer["FromSet"])
            ids.extend(answer["ToSet"])
        
            self.ids = set(ids)
            
            self.data["FromSet"] = answer["FromSet"]
            self.data["ToSet"] = answer["ToSet"]

class MultipleGeneralizationQuestion(MultiQuestion):
    """Change the questions in case a relation can be both a generalization and
        an des-individualization"""
    
    def updateIds(self):
        "Return better Ids"
        
        answer = self.db.query(self.queryIds)
        try:
            answer = answer.next()
        except StopIteration:
            self.ids = set([])
        else:
            ids = [self.data["Relation"]]
            ids.extend(answer["FromSet"])
            ids.extend(answer["ToSet"])
        
            self.ids = set(ids)

            self.data["FromSet"] = [str(el) for el in answer["FromSet"]]
            self.data["ToSet"] = [str(el) for el in answer["ToSet"]]
